Skip to content

Sync fork with upstream v0.3.0 (37 commits) - #159

Closed
joesteinkamp wants to merge 46 commits into
google-labs-code:mainfrom
joesteinkamp:sync/upstream-0.3.0
Closed

Sync fork with upstream v0.3.0 (37 commits)#159
joesteinkamp wants to merge 46 commits into
google-labs-code:mainfrom
joesteinkamp:sync/upstream-0.3.0

Conversation

@joesteinkamp

Copy link
Copy Markdown

Summary

Merges google-labs-code/design.md@main (releases 0.2.0 + 0.3.0, 37 commits) into the fork, resolving 18 conflicted files. Full analysis: the branches diverged 2026-04-22; upstream added two export formats, a spec-grade color engine, five lint rules, and a long tail of CLI hardening.

New from upstream

  • --format css-vars export; upstream's Tailwind v4 emitter kept as a library export
  • CSS Color Module parser: named colors, hwb(), lch(), color-mix() (fallback behind the fork's parser, which keeps display-p3 and format/raw round-trip metadata)
  • Nested token declarations with flattening + collision detection
  • unknown-key (Levenshtein "did you mean") and token-like-ignored lint rules; typography sub-property warnings
  • Exit-code fixes for scripted use, TTY-stdin hint, graceful ENOENT, Windows designmd alias, bounded validation cost
  • PHILOSOPHY.md, config-driven spec type docs (docs/spec.md regenerated)

Key resolutions (details in the merge commit and CHANGELOG.md)

  • Model pipeline: fork's architecture kept; upstream improvements grafted in (forEachLeaf, collision guards, symbol-table phase-2, unknown-key tracking)
  • tailwind export stays aliased to the fork's shadcn-style css-tailwind CSS (deviates from upstream's json-tailwind alias to avoid silently changing fork output); v3 JSON available at json-tailwind
  • orphaned-tokens combines fork ramp/pair provenance with upstream MD3 family heuristics
  • SCHEMA_KEYS extended with fork primitives so unknown-key stays quiet on them

Test plan

  • bun run lint clean
  • bun run test — 774 pass / 0 fail across cli + evals
  • Smoke test: lint + all 4 export formats on a DESIGN.md exercising nested colors, ramps, named colors, color-mix; typo'd top-level key gets a suggestion

🤖 Generated with Claude Code

claude and others added 30 commits April 26, 2026 03:00
Introduce two object-shaped color values that the model expands into the
existing flat colors map, plus three rules that enforce the new structure.
Flat hex colors keep working unchanged.

Schema (in YAML, additive):
  primary:                              # ramp
    type: ramp
    anchor: "#1A1C1E"
    humanName: "Boston Clay"
    pairs: { container: { bg: 100, fg: 800 } }

  surface-info:                         # standalone pair
    type: pair
    container: "#E0F2FE"
    onContainer: "#0C4A6E"

Ramps interpolate 50/100/.../900 in OKLCH (pure-TS, no new dep) preserving
chroma and hue and varying L from anchor toward white/black. Steps land in
state.colors at `<ramp>.<step>` and resolve via `{colors.<ramp>.<step>}`;
the bare ramp name still resolves to the anchor.

Pairs land in state.colors as both dotted members
(`<pair>.container`/`<pair>.onContainer`) and hyphenated flat aliases
(`<pair>` = container, `on-<pair>` = on-container). Inline-ramp pairs
synthesize M3-style flat aliases (`primary-container`,
`on-primary-container`) for back-compat with existing systems.

New rules:
- pair-contrast (error): every declared pair meets its minContrast floor
- mixed-pair-foreground (warning): a pair container backgroundColor must
  be paired with the matching on-container as textColor
- ramp-anchor-naming (warning): every ramp should declare humanName so
  prose-token-mismatch (#4) can validate "Boston Clay (#1A1C1E)" sentences

Updated rules:
- orphaned-tokens: exempts individual ramp steps and pair members so a
  single declaration doesn't flood the report; only the anchor / pair
  surfaces a warning when nothing references the group

Exporters:
- Tailwind: ramps emit as nested `{ DEFAULT, '50', ..., '900' }` objects;
  pair members emit as hyphenated flat keys
- DTCG: ramps emit as nested groups with $extensions['design.md'] vendor
  metadata and an anchor alias; standalone pairs emit as nested groups
  with role $extensions on each member

Tests: +34 (203 → 237 passing). Adds OKLCH conversion tests, model
expansion tests, three new rule tests, exporter tests for the new shapes,
and an end-to-end fixture (RAMPS_AND_PAIRS.md).

Documents the new color shapes via comments in spec-config.yaml. Spec
doc regeneration and example migration are deferred follow-ups (each
requires renderer support and fixture-test churn that warrants its
own PR).

https://claude.ai/code/session_01E3CsAihbbFG354mb8DNuGG
Promotes border, borderColor, borderWidth, shadow, elevation, gap, iconSize,
opacity, and transition from "accept with warning" to typed component sub-
tokens with per-property validators. Adds an `elevation` top-level token group
for semantic shadows (resting / raised / overlay / modal) and four new lint
rules — opacity-stacking, animating-layout-property, elevation-without-
semantics, triple-separation — that enforce the prose guidelines from the
Components section.

Tailwind exporter now projects elevation tokens to `theme.extend.boxShadow`;
the DTCG exporter emits an `elevation` shadow group. Examples (atmospheric-
glass, paws-and-paths, totality-festival) are migrated to the new vocabulary
and define elevation tokens.

https://claude.ai/code/session_01DLsGxzVXmTAwKWY4JZNn2U
…3HngO

feat(colors): ramps and pairs as schema primitives (#1)
…e-2-plan-4Oexv

# Conflicts:
#	packages/cli/src/commands/spec.test.ts
#	packages/cli/src/linter/linter/rules/index.ts
#	packages/cli/src/linter/linter/rules/types.test.ts
#	packages/cli/src/linter/model/handler.ts
#	packages/cli/src/linter/tailwind/handler.test.ts
#	packages/cli/src/linter/tailwind/handler.ts
Introduces a nested `states:` block under each component so transient
responses (hover, focus-visible, active, pressed, disabled, loading) are
modeled as inheriting overrides on a base — replacing the brittle
`*-hover` flat-sibling convention. Implements the design plan from #3.

- Schema: `component_states` (vocabulary) + per-component `interactive`
  flag and nested `states` map. Adds new sub-tokens (opacity, outline,
  boxShadow, border, cursor) so state overrides can express common
  affordance changes.
- Model: `ComponentDef` gains `states`, `resolvedStates` (base ⊕ overrides
  pre-merged so downstream consumers do not reimplement merging), and
  `interactive`.
- New linter rules: `unchanged-state` (warn), `missing-focus-visible`
  (error), `outline-none-without-replacement` (error),
  `hover-only-affordance` (warn), `disabled-opacity-only` (warn),
  `unknown-state` (warn).
- Updated rules: `broken-ref`, `contrast-ratio`, `orphaned-tokens` now
  recurse into per-state overrides.
- Exporters: tailwind opt-in `--components` flag emits a plugin object
  with `&:hover`, `&:focus-visible`, `&:active`, `&:disabled` nested
  rules suitable for `addComponents()`. DTCG emitter projects components
  into a `component` group with `$extensions['design.md'].states`.
- Examples migrated from `*-hover` siblings to nested `states.hover`
  with focus-visible and disabled added (the latter would otherwise
  surface real `missing-focus-visible` errors).
- Spec doc regenerated with a new "Interactive States" subsection
  covering state hierarchy, focus-visible discipline, disabled
  affordance, hover-is-desktop-only, state-vs-variant, and loading.

Closes #3.

https://claude.ai/code/session_018VrVVrSx1PAEnrtNwywhjq
…n-4Oexv

feat(linter): expand component property vocabulary (#2)
…ppressions

Detects drift between hex literals embedded in markdown prose and the resolved
values of color tokens. Two sub-rules under one rule name:

- orphan: any hex in prose must appear as some token's value
- anchored: when a hex is anchored to a backticked token key or a ramp
  anchor's humanName, the hex must equal that token's resolved value

Adds a generic comment-directive suppression mechanism
(disable-next-line / disable+enable / disable-file) parsed by the markdown
parser and consulted per-finding. Sections now carry startLine/endLine and
fenced code-block ranges so prose-aware rules can target source positions
and skip illustrative blocks.

Closes #4
feat(linter): add prose-token-mismatch rule with suppression directives
Merges PRs #10 (color ramps and pairs) and #11 (typed property
vocabulary, elevation tokens) from main into the states branch.

Key reconciliation points:
- spec-config.yaml: merged sub-token vocabulary so the combined set covers
  border/borderColor/shadow/elevation/gap/iconSize/opacity/transition (from
  #11) plus outline/boxShadow/cursor (needed for state overrides).
- model/handler.ts: combined the typed-validator + elevation-shorthand
  resolution from main with the per-state override merging from the
  states branch. `interactive` and `states` are skipped in the property
  loop and validators run per state-override too.
- DTCG exporter: emits both the elevation group (main) and the
  component group with `$extensions['design.md'].states` (this branch).
- Tailwind exporter test: ramps/pairs/elevation tests + components plugin
  test all live alongside.
- Example design files: merged main's typed properties (transition,
  shadow, border, iconSize) onto each component's base, with the state
  branch's `interactive: true` + `states:` block layered on top.
  `transparent` literal replaced with `#00000000` to satisfy the typed
  Color validator.

All 328 tests pass; spec.md regenerated; design_tokens.json regenerated.

https://claude.ai/code/session_018VrVVrSx1PAEnrtNwywhjq
Resolves three rule-count conflicts so both main's prose-token-mismatch
rule and the PR's six new state-related rules land together (total 22):
- packages/cli/src/linter/linter/rules/index.ts
- packages/cli/src/linter/linter/rules/types.test.ts
- packages/cli/src/commands/spec.test.ts

bun test: 349/349 pass.

https://claude.ai/code/session_018VrVVrSx1PAEnrtNwywhjq
…3UH7

Resolve merge conflicts for PR #12 (component states)
Closes the open-world component schema with an opt-in registry. When the
`components:` block uses the `registry: + definitions:` shape, five new
rules engage:

- `unbound-component` (error) — flags definitions and `{components.X}`
  prose references whose name isn't in the registry.
- `missing-required-property` (error) — `requiredProperties` on a
  registry entry must be set by the matching definition; composed
  properties count.
- `registry-without-definition` (warning) — registry entries that lack
  a matching definition.
- `composes-cycle` (error) — cycles in the `composes:` graph.
- `naming-convention` (warning) — registry names follow `noun-modifier`
  with a closed modifier vocabulary.

The flat `components:` shape continues to work unchanged; the registry
is fully opt-in. Adds `kind` (button/input/container/display/navigation/
overlay) which drives default interactivity, and `composes:` for
property pre-merge before child overrides. Documents the registry
authoring discipline in the Components section of the spec.

Defers source-side scanning (`--src` flag), exporters changes
(Tailwind/DTCG), and the bulk migration of existing example files —
these are independent rollout phases listed in the design doc.
Resolved conflicts so the new registry feature (#6) coexists cleanly
with the recently-merged first-class component states feature (#14).
Both streams are largely orthogonal — registry is the closed-world set
of component names, states are per-component variants — and combine in
the natural way:

- spec-config.ts now exports both COMPONENT_KINDS/COMPONENT_MODIFIERS
  (registry) and COMPONENT_STATES (states); the SpecConfig surface
  bundles both.
- parser/spec.ts: keep RawRegistryEntry alongside RawComponentValue so
  definitions can carry the nested states block while still being
  validated against the registry.
- parser/handler.ts: normalizeComponents now produces the
  RawComponentValue-shaped definitions map, preserving registry
  detection.
- DEFAULT_RULE_DESCRIPTORS aggregates 16 baseline + 6 state + 5
  registry = 27 rules.
- spec.mdx: registry section ("Component Registry (Closed-World)")
  precedes the new "Interactive States" section in the Components
  documentation.
- docs/spec.md regenerated from the merged spec.mdx.

All 383 tests pass.
…n-hmt8L

feat(linter): component registry + closed-world rules (#6)
Closes #5. Two new top-level token groups land in the spec:

- motion: duration (ms/s), easing (CSS keywords + cubic-bezier with
  control-point extraction), and a required reducedMotion fallback.
  Embedded refs in component transition shorthands resolve inline so
  exporters see literal values.
- iconography: a single library reference (closed enum: lucide,
  material-symbols, heroicons, phosphor, custom-svg), stroke weight,
  size scale, default size, and color-binding rule.

Pipeline changes:
- spec-config.yaml grows the two groups + Motion / Iconography canonical
  sections (inserted between Layout/Elevation and Shapes/Components).
- ParsedDesignSystem and DesignSystemState extend with the new shapes;
  ResolvedDuration and ResolvedEasing join the ResolvedValue union.
- ModelHandler gains parseMotion / parseIconography plus an embedded-ref
  resolver invoked on component shorthand strings.
- Tailwind exporter emits transitionDuration and transitionTimingFunction.
- DTCG exporter emits motion.duration as duration-typed tokens, easings
  as cubicBezier (4-tuple for parsed beziers, string + extension for
  keywords), reducedMotion under the design.md vendor extension, and
  iconography.sizes as a dimension group with the metadata under
  $extensions['design.md'].iconography.

New lint rules:
- missing-reduced-motion (warning) — motion declared without a
  reducedMotion fallback.
- overlong-duration (warning) — anything over 400ms reads as broken.
- icon-size-off-scale (warning) — component iconSize literals that don't
  match an iconography.sizes entry.

Existing rules adapt: token-summary counts motion + icon-size tokens;
orphaned-tokens uses ComponentDef.referencedTokens (recorded
pre-resolution) to avoid false positives now that embedded refs get
substituted; broken-ref already covers unresolved embedded refs via
the existing unresolvedRefs channel.

Examples: paws-and-paths gains Motion + Iconography sections wired to
real component transitions and iconSize references; tailwind.config.js
and design_tokens.json regenerated.

https://claude.ai/code/session_01Poxc1vkHRW2UdBQzS1gv2h
Brings the motion + iconography work in alignment with the new component
states / closed-world registry features that landed on main.

Conflict resolutions:
- spec-config.yaml/.ts: union of icon_libraries + easing_keywords
  (mine) and component_kinds + component_modifiers + ComponentExample
  schema (theirs); examples.button-primary keeps my motion transition
  AND theirs interactive/states block.
- parser/spec.ts + handler.ts: union of RawMotionDef/RawIconographyDef
  (mine) and RawRegistryEntry/RawComponentValue + componentRegistry
  (theirs).
- model/handler.ts: imports unioned; Phase 3 component loop combines
  mine (referencedTokens tracker) with theirs (interactive flag,
  states map, resolveComponentValue helper). Embedded-ref resolution
  for transition shorthands moved into resolveComponentValue's
  fallback branch so it composes with theirs.
- linter/rules/index.ts + tests: rule descriptors unioned (count
  becomes 30: theirs' 27 plus mine 3); test asserts updated.
- dtcg/handler.ts: union of mapMotion/mapIconography (mine) and
  mapComponents (theirs).
- examples/paws-and-paths/DESIGN.md: each conflicting component keeps
  the motion+iconography refs from mine AND the interactive/states
  blocks from theirs. design_tokens.json + tailwind.config.js
  regenerated.
- docs/spec.md: regenerated from the unioned spec.mdx + spec-config.

Tests pass (408), build is clean.

https://claude.ai/code/session_01Poxc1vkHRW2UdBQzS1gv2h
…n-Kngwg

Add motion and iconography token support to design system
…r rules (#8)

Cover the verbal half of brand identity. The eight prescribed sections
are entirely visual; two products with identical DESIGN.md files and
different brands sounded identical until now. This change adds
machine-readable voice + copy primitives, a Voice prose section between
Typography and Layout, and six new linter rules that enforce them.

Schema:
- `voice:` — formality / warmth / authority / playfulness axes (1–5),
  plus person, tense, oxfordComma, contractions.
- `copy:` — per-surface casing, buttonLabelMaxWords, errorPattern,
  emptyStateTone, bannedTerms, bannedRegex, approvedTerms,
  reservedNames, titleCase overrides.

Model:
- `parseVoice` / `parseCopy` validate axis ranges, casing enums, and
  pre-compile bannedRegex once per build.
- Voice/copy keys are surfaced in the symbol table so
  `{voice.warmth}` style references resolve through the existing
  broken-ref machinery.

Linter rules:
- banned-term-in-prose — flags banned terms / regexes in prose and
  component label-bearing properties (label, placeholder, title,
  aria-label). Whole-word literal match; phrase match for terms with
  spaces.
- button-exceeds-word-limit — flags button labels over the configured
  cap. Soft-depends on the registry's `kind: button`; falls back to a
  name-prefix heuristic.
- casing-mismatch — verifies labels match copy.casing.<surface>
  (button / nav / section-heading). Sentence-case, title-case,
  UPPERCASE, lowercase. knownProperNouns + titleCase.exceptions
  control false positives.
- approved-term-violation — flags `user` where `customer` is required.
- reserved-name-form — flags lowercased / hyphenated variants of
  reserved product names.
- error-pattern-violation — checks error-message components against
  copy.errorPattern's slot count + literal separators.

Sections + outputs:
- CANONICAL_ORDER inserts Voice between Typography and Layout, with
  `Tone` and `Voice & Tone` aliases.
- token-summary now reports the banned-term count.
- DTCG exporter emits voice + copy under
  `$extensions['design.md']` so downstream tools consume without
  losing fidelity.
- spec.mdx has a Voice section narrative + axes/casing tables;
  docs/spec.md regenerated.

Examples:
- atmospheric-glass: formal, cold, technical voice (formality 4,
  warmth 2, third person, contractions avoided).
- paws-and-paths: warm consumer voice (formality 2, warmth 5, second
  person, contractions permitted), with `user → pet parent` approved
  term and an encouraging empty-state tone.
- totality-festival: cinematic / dramatic (playfulness 4, UPPERCASE
  buttons + nav).

Other:
- Frontmatter is now part of the parser's code-block ranges so
  prose-aware rules don't scan YAML.
- New VOICE.md fixture covers end-to-end parsing + rule firing.
- 6 new rule unit tests + voice/copy model tests in handler.test.ts.

https://claude.ai/code/session_01Jig9jMBKFgijyTtk1VdFEo
…sity)

Implements the foundation laid out in #7's run-plan. Themes declare
overrides on top of the implicit `light` base; resolution deep-merges
them per-theme so the same token name can carry different values across
modes. Two new parity rules surface the common failure modes; the
existing contrast-ratio rule now runs per theme against per-theme
contrast targets; Tailwind output gains a `themes` field that mirrors
the base shape per theme.

Schema (spec-config.yaml):
- New `Themes` canonical section (alias: `Modes`, `Themes & Modes`).
- `well_known_themes` informational list (light, dark, high-contrast,
  comfortable, compact). The list is open — authors may declare other
  theme names.

Parser:
- `RawThemeDef` carries `inheritsFrom`, color/typography/rounded/spacing/
  elevation overrides, and a `contrastTarget`.
- `ParsedDesignSystem.themes` passes through unchanged YAML.

Model:
- `ThemeView` carries a fully resolved per-theme view (colors, ramps,
  pairs, typography, spacing, rounded, elevation, contrastTarget,
  explicitColorOverrides). The implicit `light` base is always present.
- `DesignSystemState.themes` and `activeTheme` join the existing fields.
  Top-level fields still mirror the active theme so existing rules keep
  working unchanged.
- Theme resolution honors `inheritsFrom` chains, breaks cycles with a
  warning, and re-runs ramp / pair expansion within a per-theme symbol
  table so `{colors.primary}` resolves theme-locally.
- ComponentDef gains `propertyRefs` and `stateRefs` so per-theme rules
  can re-resolve color references through any theme view.

Rules:
- `theme-parity` (warning) — every base color must be explicitly
  overridden in each declared theme.
- `pair-parity` (warning) — half-overriding a color pair across themes
  silently breaks the contrast contract.
- `contrast-ratio` runs per theme against the theme's `contrastTarget`
  (defaults to WCAG AA; high-contrast themes typically raise body to 7).

Tailwind exporter:
- `mapColors` becomes view-driven; per-theme overrides emit under a new
  `themes` field on the result. Authors who don't declare themes see no
  output change.

Examples:
- `examples/paws-and-paths` gains a `themes.dark` palette with a
  desaturated primary container, deeper night-blue surfaces, and per-
  theme elevations. New `## Themes` prose covers the "don't invert,
  reconsider" / saturation-discipline / contrast-target guidelines.

Out of scope (follow-ups):
- saturation-discipline, density-rule-violation,
  theme-elevation-mapping rules
- DTCG `$extensions['design.md'].themes` themed sets
- CLI `--theme` / `--themes-split` flags
- spec-gen renderer + spec.mdx Themes section
- broken-ref / orphaned-tokens cross-theme awareness
- Migration of atmospheric-glass and totality-festival examples

Closes #7 — phase 1, 2, 3 (partial), 5 (basic), 7 (one example).

https://claude.ai/code/session_01CzQLFXGrZ372zTigJu2uu7
feat(themes): first-class theme primitive (dark / high-contrast / density)
…+copy branch

Resolves conflicts with origin/main where the voice+copy primitives
(this branch) and the themes / motion / iconography primitives (main)
both touched: spec-config.yaml, spec-config.ts, parser & model spec/
handler, dtcg handler, spec-gen sources, and the paws-and-paths
example.

Resolution approach: keep both feature sets additively. Specifically:
- Section order is now Overview → Colors → Typography → Voice → Themes
  → Layout → Motion → Elevation & Depth → Shapes → Iconography →
  Components → Do's/Don'ts.
- DesignSystemState carries voice/copy *and* themes/activeTheme/
  motion/iconography.
- DEFAULT_RULE_DESCRIPTORS now totals 38 rules (27 base + 6 voice/copy
  + 5 motion/icon/theme); types.test.ts and spec.test.ts updated.
- DTCG handler emits both the voice/copy `$extensions` and the motion/
  iconography token types.
- paws-and-paths example carries voice + copy + dark theme overrides.
- docs/spec.md regenerated.

All 482 tests pass.

https://claude.ai/code/session_01Jig9jMBKFgijyTtk1VdFEo
feat(voice+copy): add `voice:` and `copy:` primitives + content linter rules (#8)
Adds a unified Responsive & Layout primitive set so layout becomes a
contract instead of a per-screen improvisation:

- `breakpoints` (philosophy + values), `grid` (columns + gutter + margin
  + maxWidth + bleedExceptions), `layoutRules` (contentMaxWidth, stack
  spacing, formFieldWidth), `templates` (page-level region registry),
  `pages` (route → template assignments).
- Five new linter rules: breakpoint-monotonicity (error),
  unknown-template (error), missing-region (error),
  off-grid-dimension (warn), template-region-purity (warn).
- Tailwind exporter now emits theme.screens, theme.maxWidth (container,
  prose), and theme.container.padding from grid.margin.
- DTCG exporter emits breakpoints as a sibling dimension group and
  surfaces grid / layoutRules / templates / pages under
  $extensions['design.md'].
- Layout section in spec.mdx restructured with required prose
  subsections (mobile-first, breakpoint philosophy, grid usage,
  readable measure, page templates, region semantics).
- paws-and-paths example migrated to declare three templates and
  populate the new prose subsections; regenerated tokens + tailwind.

Source-side scanning (data-template / data-region) is intentionally
deferred until issue #6's --src scanner lands.

https://claude.ai/code/session_01Ph1qdmfpqFnUUFzEG99CoF
feat(layout): responsive, grid, page templates, layout rules (#9)
Closes the top three friction points surfaced by the Impeccable comparison —
all spec/CLI hygiene that needed to land before any DESIGN.md emitter.

Item 1: modern color formats
- New `model/color.ts` parses hex, rgb()/rgba(), hsl()/hsla(), oklch(),
  oklab(), lab(), and color(display-p3 …). Wide-gamut sources are converted
  through linear sRGB so WCAG luminance and contrast checks stay accurate.
- ResolvedColor now carries `format` and `raw`; the Tailwind exporter
  round-trips the original notation so Tailwind v4 can keep oklch / p3 intent.
- Validation helpers (isValidColor, isParseableDimension) tolerate non-string
  inputs so YAML scalars like `opacity: 1` no longer crash the model layer.

Item 2: extended component sub-tokens
- Added gap, border, outline, opacity, boxShadow, transition, backdropFilter
  to the allowlist. Renderer now surfaces the per-token description in the
  generated spec, matching the typography list.
- New EXTENDED_COMPONENTS.md fixture exercises every new sub-token alongside
  oklch / display-p3 / hsl colors and lints clean.

Item 3: fixer wired into the CLI
- New `design.md fix` command (stdout by default, --write rewrites in place).
- `design.md lint --fix` runs section-order auto-fix and writes back.
- README and docs/spec.md regenerated.
…ble-style-8npam

# Conflicts:
#	docs/spec.md
#	packages/cli/src/linter/fixture.test.ts
#	packages/cli/src/linter/linter/rules/contrast-ratio.test.ts
#	packages/cli/src/linter/model/handler.ts
#	packages/cli/src/linter/model/spec.ts
#	packages/cli/src/linter/spec-config.yaml
#	packages/cli/src/linter/tailwind/handler.ts
…le-8npam

feat: accept modern color formats, expand sub-tokens, wire up fixer
Adds a packages/evals workspace that measures whether DESIGN.md actually
helps coding agents produce on-brand UIs, not just whether daily-driving
feels good.

Methodology:
- Loop over (design × task × format × agent).
- Format is the A/B: designmd | prose | dtcg | none.
- Score each output by color-palette match, font-family match, and
  spacing-scale snap; aggregate per format.
- Two mock agents (token-aware + off-brand) self-test the scorer so the
  harness runs end-to-end with no API key.

The claudeAgent slot is stubbed; wire in @anthropic-ai/sdk to run for
real. README documents the methodology, limitations, and the path to a
visual-fidelity scorer.

https://claude.ai/code/session_01GCBZWo6ghh7YfCpE2eFJFm
…g-nIL7z

Sketch eval harness for DESIGN.md fidelity
Tailwind v4 deprecates tailwind.config.js in favor of CSS-first
configuration. The export command now emits a `@theme` stylesheet
(--color-*, --font-*, --text-*, --radius-*, --spacing-*, --shadow-*,
--breakpoint-*, --container-*, --ease-*, --duration-*) instead of a
JSON theme.extend object. Examples are regenerated as theme.css.

https://claude.ai/code/session_013P12rj4w9pd6FQryAy8YpP
claude and others added 16 commits May 2, 2026 01:39
Restructure the CSS exporter to mirror shadcn/ui's globals.css shape:
color tokens are declared once in :root with semantic names, per-theme
overrides flow through .dark / .high-contrast selectors that swap the
same variables, and `@theme inline` aliases each one to Tailwind's
--color-* namespace via var(). Non-color tokens (typography, radius,
spacing, shadow, breakpoint, motion) emit directly inside @theme.

This enables runtime theme switching with a single class toggle without
rebuilding the Tailwind theme — flipping `.dark` updates every utility
that consumes `--color-*` because the underlying `--*` variables change.

https://claude.ai/code/session_013P12rj4w9pd6FQryAy8YpP
…YdUd

Migrate Tailwind config to v4 CSS @theme approach
…ntion

Pair `on-container` members previously emitted as `on-<pair>` in the
Tailwind theme (`--on-primary`, `bg-on-primary`). Rename to
`<pair>-foreground` so generated utilities match the shadcn/ui Tailwind v4
convention (`text-primary-foreground`, `--primary-foreground`).

The DESIGN.md schema is unchanged — pairs still use `container` /
`onContainer` keys, preserving `minContrast` validation and the
`mixed-pair-foreground` lint. The rename is applied only at the Tailwind
emitter boundary using `pairRole.role === 'on-container'`.

https://claude.ai/code/session_01ACjio3fzvcgvZoR7vbnq7g
…ection

- Prepend `@import "tailwindcss";` and `@custom-variant dark (&:is(.dark *));`
  so the generated stylesheet is drop-in for shadcn/ui projects.
- Lift `borderRadius` tokens out of `@theme` and into `:root` as
  `--rounded-<name>`; alias them in `@theme inline` as
  `--radius-<name>: var(--rounded-<name>)`. This mirrors the colors flow
  and lets per-theme blocks override radius at runtime via the same
  CSS-variable indirection.

Dark theme support is unchanged but worth noting: when a DESIGN.md
declares `themes.dark.colors`, the emitter already renders a `.dark { ... }`
override block (verified against examples/paws-and-paths).

https://claude.ai/code/session_01ACjio3fzvcgvZoR7vbnq7g
…ists

Many DESIGN.md files (e.g. examples/paws-and-paths) use Material-style flat
naming — `primary` + `on-primary` — rather than declaring a `type: pair`
block. Previously the shadcn rename only fired for declared pairs, so those
files emitted `--on-primary` instead of `--primary-foreground`.

Extend the rename to also fire when an `on-<base>` color has a sibling
flat color or ramp named `<base>`. This produces shadcn-shaped output for
both pair-declared and M3-flat sources without changing the YAML schema or
losing any existing lint coverage.

https://claude.ai/code/session_01ACjio3fzvcgvZoR7vbnq7g
…e-azLsy

tailwind: rename pair foregrounds to shadcn `<pair>-foreground` convention
The existing harness only measured whether agent-rendered colors and font
families landed in the palette. This adds three more scoring layers that the
README previously called out as missing:

- copy: synthesizes a virtual DesignSystemState from the agent's HTML
  (buttons, headings, nav links become components; paragraph text becomes a
  prose section) and runs the existing linter copy descriptors —
  banned-term, approved-term, button-word-limit, error-pattern, casing,
  reserved-name — against it.
- semantic: deterministic per-element assertions on each Task
  (e.g. <button> background must resolve to {colors.primary}). The runner
  parses inline style, resolves token references, and compares with the
  existing color/dimension tolerances.
- structural / vision: linkedom-based selector-presence check (always cheap),
  plus an opt-in Playwright screenshot + Claude Sonnet 4.6 vision judge
  behind --screenshots / --vision-judge.

Aggregate now drops absent subscores from both numerator and denominator so
disabling a layer doesn't dilute the result. The mock token-aware agent now
looks up named tokens (primary, on-primary, surface, on-surface) so it can
honor semantic assertions in designmd/dtcg formats and visibly fail in
prose/none — preserving the harness self-test.

Also exposes the copy rule descriptors from the public @google/design.md/linter
entry so the eval package can call them via the package alias.

https://claude.ai/code/session_01RrBJ3FJ5KNCkVg4uuQoLLN
evals: add copy, semantic, and vision layers on top of token extraction
This fork isn't published to npm, so `npx @google/design.md` runs the
upstream binary without the fork's lint rules and eval changes. Replace
all CLI invocations with `bun run cli ...` and document the clone +
`bun install` setup.

https://claude.ai/code/session_01QZkmJF72BrdVRuxdWXwwW9
…mmand-7I2Gj

docs(readme): switch CLI examples to local-clone bun workflow
…KpGbh

docs(readme): add fork overview section
Adds unit tests for the highest-correctness-risk modules identified in
the coverage audit:

- packages/evals/src/score.test.ts: color normalization/distance math,
  token resolution, dimension scoring, subscore aggregation.
- packages/evals/src/semantic.test.ts: DOM-driven CSS assertions
  including token references, font-family case-insensitivity, minFontSize
  tolerance, structural element checks.
- packages/evals/src/copy.test.ts: virtual component synthesis from HTML
  and copy-rule scoring against the paws-and-paths fixture.
- packages/evals/src/vision.test.ts: judge-response parsing including
  fenced JSON, clamping, and unparseable input.
- packages/cli/src/linter/tailwind/css.test.ts: Tailwind v4 CSS rendering
  for flat colors, radii, typography metadata, per-theme overrides, and
  the failed-result throw path.

Also exports parseJudgeResponse from vision.ts so it can be unit-tested
without mocking the Anthropic SDK.

Suite: 577 -> 673 tests, all passing.

https://claude.ai/code/session_01DMQPxqRYzUA5xyV7FY4dCh
…-SsR3L

test(evals,tailwind): cover scoring engine and CSS renderer
Brings in 37 upstream commits since the April divergence (releases 0.2.0
and 0.3.0): the css-vars export format, the CSS Color Module parser
(named colors, hwb, lch, color-mix), nested token declarations with
collision detection, the unknown-key / token-like-ignored lint rules,
typography sub-property warnings, export/lint exit-code fixes, TTY and
ENOENT guards, the Windows designmd alias, and PHILOSOPHY.md.

Conflict resolutions of note:
- model/handler.ts keeps the fork's pipeline (ramps, pairs, themes,
  voice, layout) with upstream's improvements grafted in: forEachLeaf
  nested-token flattening, per-category collision guards, symbol-table
  driven phase-2 resolution (extended to elevation), unknown-key
  tracking, and defensive dimension parsing.
- Color parsing accepts the union of both engines: the fork's parser
  (incl. display-p3) first, upstream's parseCssColor as fallback for
  named colors, hwb(), lch(), and color-mix(); ColorFormat gains 'css'.
- export keeps the fork's shadcn-style Tailwind v4 CSS emitter under
  css-tailwind, with `tailwind` aliasing it (not json-tailwind as
  upstream chose); upstream's v3 JSON emitter lives at json-tailwind
  and upstream's TailwindV4EmitterHandler remains a library export.
- orphaned-tokens combines the fork's ramp/pair provenance exemptions
  with upstream's MD3 family heuristics.
- SCHEMA_KEYS extended with the fork's schema (themes, voice, copy,
  motion, iconography, layout, registry) so unknown-key stays quiet.
- Upstream tests asserting raw number/boolean component scalars were
  adapted to the fork's coerce-to-string contract; 'red' is now a valid
  color, so validator tests assert acceptance instead of rejection.

All 774 workspace tests pass; docs/spec.md regenerated via spec:gen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joesteinkamp

Copy link
Copy Markdown
Author

Opened against the wrong repo (this was meant for the fork). Apologies for the noise.

@google-cla

google-cla Bot commented Jul 24, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@joesteinkamp
joesteinkamp deleted the sync/upstream-0.3.0 branch July 24, 2026 03:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants